package com.yricky.astral.structure.scope


import com.yricky.astral.project.Hazuki
import com.yricky.astral.project.Hazuki.Rules.SelectionMissElseBranch
import com.yricky.astral.signal.Signal
import com.yricky.astral.signal.ensureSameWidth
import java.lang.Integer.max

interface SignalLogic:Hazuki.Checkable,SignalUtils {

    /**
     * And for each bit.
     *
     * Example:
     *
     * Astral code: s1 and s2
     *
     * Generated verilog: (s1 & s2)
     *
     * @see lAnd
     * @see sAnd
     */
    infix fun Signal.and(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = s.width
            override fun generateBody(): String = "($body & ${s.generateBody()})"
        }
    }

    /**
     * Or for each bit.
     *
     * Example:
     *
     * Astral code: s1 or s2
     *
     * Generated verilog: (s1 | s2)
     *
     * @see lOr
     * @see sOr
     */
    infix fun Signal.or(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = s.width
            override fun generateBody(): String = "($body | ${s.generateBody()})"
        }
    }

    /**
     * Xor for each bit.
     *
     * Example:
     *
     * Astral code: s1 xor s2
     *
     * Generated verilog: (s1 ^ s2)
     *
     * @see sXor
     */
    infix fun Signal.xor(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = s.width
            override fun generateBody(): String = "($body ^ ${s.generateBody()})"
        }
    }

    /**
     * Invert each bit.
     *
     * Example:
     *
     * Astral code: s1.inv()
     *
     * Generated verilog: (~s1)
     *
     * @see lInv
     */
    fun Signal.inv(): Signal {
        val body = generateBody()
        val w = width
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = w
            override fun generateBody(): String = "(~$body)"
        }
    }

    /**
     * Shrink a signal to 1 bit using &.
     *
     * Example:
     *
     * Astral code: s1.sAnd()
     *
     * Generated verilog: (&s1)
     *
     * @see and
     * @see lAnd
     */
    fun Signal.sAnd(): Signal {
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "(&$body)"
        }
    }

    /**
     * Shrink a signal to 1 bit using |.
     *
     * Example:
     *
     * Astral code: s1.sOr()
     *
     * Generated verilog: (|s1)
     *
     * @see or
     * @see lOr
     */
    fun Signal.sOr(): Signal {
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "(|$body)"
        }
    }

    /**
     * Shrink a signal to 1 bit using ^.
     *
     * Example:
     *
     * Astral code: s1.sXor()
     *
     * Generated verilog: (^s1)
     *
     * @see xor
     */
    fun Signal.sXor(): Signal {
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "(^$body)"
        }
    }

    /**
     * Logic and between two signal, width of result is 1.
     *
     * Example:
     *
     * Astral code: s1 lAnd s2
     *
     * Generated verilog: (s1 && s2)
     *
     * @see and
     * @see sAnd
     */
    infix fun Signal.lAnd(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "($body && ${s.generateBody()})"
        }
    }

    /**
     * Logic or between two signal, width of result is 1.
     *
     * Example:
     *
     * Astral code: s1 lOr s2
     *
     * Generated verilog: (s1 || s2)
     *
     * @see or
     * @see sOr
     */
    infix fun Signal.lOr(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "($body || ${s.generateBody()})"
        }
    }

    /**
     * Logic equal between two signal, width of result is 1.
     *
     * Example:
     *
     * Astral code: s1 eq s2
     *
     * Generated verilog: (s1 == s2)
     */
    infix fun Signal.eq(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "($body == ${s.generateBody()})"
        }
    }

    /**
     * Logic unequal between two signal, width of result is 1.
     *
     * Example:
     *
     * Astral code: s1 uEq s2
     *
     * Generated verilog: (s1 != s2)
     */
    infix fun Signal.uEq(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "($body != ${s.generateBody()})"
        }
    }

    /**
     * More than between two signal, width of result is 1.
     *
     * Example:
     *
     * Astral code: s1 mt s2
     *
     * Generated verilog: (s1 > s2)
     */
    infix fun Signal.mt(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "($body > ${s.generateBody()})"
        }
    }

    /**
     * Less than between two signal, width of result is 1.
     *
     * Example:
     *
     * Astral code: s1 lt s2
     *
     * Generated verilog: (s1 < s2)
     */
    infix fun Signal.lt(s: Signal): Signal {
        ensureSameWidth(s)
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "($body < ${s.generateBody()})"
        }
    }


    /**
     * Logic invert, width of result is 1.
     *
     * Example:
     *
     * Astral code: s1.lInv()
     *private
     * Generated verilog: (!s1)
     *
     * @see inv
     */
    fun Signal.lInv(): Signal {
        val body = generateBody()
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = 1
            override fun generateBody(): String = "(!$body)"
        }
    }

    /**
     * Selection logic.
     *
     * Example:
     *
     * Astral code: sel( a.sAnd() to 8.w(4) , b.sAnd() to 7.w(4) , elseTo(5.w(4)) )
     *
     * Generated verilog: (&a) ? 4'h8 : (&b) ? 4'h7 : 4'h5
     *
     * Notice: Width of condition signals must be 1, Branch signals must have same width.
     *
     * Notice: last item of params must be generated by elseTo()
     * @param sr list of condition signal to branch signal.
     * @see to
     * @see elseTo
     *
     */
    fun sel(vararg sr: _SelRule): Signal {
        return sel(sr.asList())
    }


    fun sel(sr: List<_SelRule>): Signal {
        val sb = StringBuilder()
        var missLastBranch = false
        if (sr.size < 2) {
            throw IllegalArgumentException("Count of branches must more than 1")
        }
        val w = sr.first().second.width
        sb.append("(")
        sr.forEachIndexed { index, pair ->
            if (w != pair.second.width)
                throw IllegalStateException("Width of each branch should be same!")
            if ((index == sr.size - 1) and (pair.first !is LastSelectBranch)) {
                standard.check(SelectionMissElseBranch,"Last branch of selection should be \"elseTo\" branch!",Hazuki.CheckLevel.confirm)
                missLastBranch = true
            } else if ((pair.first is LastSelectBranch) and (index != sr.size - 1)) {
                throw IllegalArgumentException("\"elseTo\" branch must be the last branch of selection!")
            }
            sb.append(
                if (pair.first !is LastSelectBranch) {
                    "${pair.first.generateBody()} ? ${pair.second.generateBody()} :"
                } else {
                    " ${pair.second.generateBody()}"
                }
            )
        }
        if(missLastBranch)
            sb.append(0.w(w).generateBody())
        sb.append(")")
        return object : SignalUtils.InnerSignal() {
            override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard
            override val width: Int = w
            override fun generateBody(): String = sb.toString()
        }
    }

    /**
     * Crate a branch of selection logic.
     * @see sel
     */
    infix fun Signal.to(s: Signal): _SelRule {
        if (width != 1) {
            throw IllegalStateException("Width of condition signal must be 1!")
        }
        return _SelRule(this, s)
    }

    /**
     * Crate the last branch of selection logic.
     * @see sel 's last branch must be crated by this function.
     */
    infix fun elseTo(s: Signal): _SelRule {
        return _SelRule(object:SignalUtils.InnerSignal(),LastSelectBranch{ override val standard: Hazuki.HazukiStandard = this@SignalLogic.standard }, s)
    }

    private interface LastSelectBranch:Signal{
        override val width: Int get() = 1
        override fun generateBody(): String ="1'b1"
    }

}
private typealias _SelRule = Pair<Signal,Signal>

interface SignalLogicNotGood:Hazuki.Checkable,SignalUtils{
    /**
     * Add two signals.
     *
     * Example:
     *
     * Astral code: s1 add s2
     *
     * Generated verilog: (s1 + s2)
     *
     * Width of result is max of two signals' width.
     */
    infix fun Signal.add(s:Signal):Signal{
        println("Verilog default adder used: ${generateBody()} + ${s.generateBody()} !")
        val body = generateBody()
        val thisW = width
        return object :SignalUtils.InnerSignal(){
            override val standard: Hazuki.HazukiStandard = this@SignalLogicNotGood.standard
            override val width: Int = max(thisW,s.width)
            override fun generateBody(): String = "($body + ${s.generateBody()})"
        }
    }

    /**
     * s1 subtract s2
     *
     * Example:
     *
     * Astral code: s1 sub s2
     *
     * Generated verilog: (s1 - s2)
     *
     * Width of result is max of two signals' width.
     */
    infix fun Signal.sub(s:Signal):Signal{
        println("Verilog default adder used: ${generateBody()} + ${s.generateBody()} !")
        val body = generateBody()
        val thisW = width
        return object :SignalUtils.InnerSignal(){
            override val standard: Hazuki.HazukiStandard = this@SignalLogicNotGood.standard
            override val width: Int = max(thisW,s.width)
            override fun generateBody(): String = "($body - ${s.generateBody()})"
        }
    }
}
